/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package it.tukano.swingdeco.wizard;

import it.tukano.collections.ArrayListExt;
import it.tukano.collections.ListExt;
import it.tukano.fun.Function0;
import it.tukano.fun.Tuple2;
import it.tukano.swingdeco.ExtUtilities;
import it.tukano.swingdeco.containers.JPanelExt;
import it.tukano.swingdeco.controls.JButtonExt;
import it.tukano.swingdeco.text.JTextFieldExt;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.Timer;

/**
 *
 * @author utente4
 */
public class WizardDialog {
    
    private JDialog dialog;
    private Timer poller;
    private Runnable onCommit;
    private final JButtonExt next = new JButtonExt("Next")
            .onAction(new Runnable() {public void run() {
                WizardDialog.this.displayNextCardOrCommit();
            }});
    private final JButtonExt prev = new JButtonExt("Previous")
            .onAction(new Runnable() {public void run() {
                WizardDialog.this.displayPreviousCardIfPossible();
            }});;
    private final JButtonExt canc = new JButtonExt("Cancel")
            .onAction(new Runnable() {public void run() {
                WizardDialog.this.stopPollerAndCloseDialog();
            }});
    private String nextLabel = "Next";
    private String endLabel = "End";
    private final ListExt<Tuple2<Component, Function0<Boolean>>> cards = new ArrayListExt<Tuple2<Component, Function0<Boolean>>>();
    private final JPanelExt buttonsContainer = new JPanelExt(new FlowLayout(FlowLayout.TRAILING))
            .append(prev, next, canc);
    private final JPanelExt bottomBar = new JPanelExt(new BorderLayout())
            .append(buttonsContainer, BorderLayout.CENTER)
            .append(new JSeparator(JSeparator.HORIZONTAL), BorderLayout.NORTH);
    private final JPanelExt display = new JPanelExt(new GridLayout(1, 1));
    private final JPanelExt mainComponent = new JPanelExt(new BorderLayout())
            .append(bottomBar, BorderLayout.SOUTH, display, BorderLayout.CENTER);
    private final Map<Component, Runnable> onDisplayFunctions = new HashMap<Component, Runnable>();
    private final Map<String, Runnable> onDisplayNameFunctions = new HashMap<String, Runnable>();
    
    public void setButtonLabels(String nextLabel, String prevLabel, String cancLabel, String endLabel) {
        next.setText(nextLabel);
        prev.setText(prevLabel);
        canc.setText(cancLabel);
        this.endLabel = endLabel;
        this.nextLabel = nextLabel;
    }
    
    public void addNamedCard(String name, Component component, String borderLayoutPosition, Function0<Boolean> committer) {
        JPanelExt card = new JPanelExt(new BorderLayout());
        card.add(component, borderLayoutPosition);
        card.setName(name);
        cards.add(Tuple2.newInstance((Component) card, committer));
    }
    
    public void addCard(Component component, String borderLayoutPosition, Function0<Boolean> committer) {
        JPanelExt card = new JPanelExt(new BorderLayout());
        card.add(component, borderLayoutPosition);
        card.setName(component.getName());
        cards.add(Tuple2.newInstance((Component) card, committer));
    }
    
    public void showDialog(Component parent, String title, Dimension size, Runnable onCommit) {
        final Frame frame = JOptionPane.getFrameForComponent(parent);
        this.onCommit = onCommit;
        this.dialog = new JDialog(frame, true);
        dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) {
            dialog.dispose();
            poller.stop();
        }});
        this.poller = new Timer(100, new ActionListener() { public void actionPerformed(ActionEvent e) {
                WizardDialog.this.changeButtonStatedForCurrentCardCommitter();
        }});
        poller.start();
        displayNextCard();
        dialog.add(mainComponent);
        if(size != null) dialog.setSize(size); else dialog.pack();
        dialog.setTitle(title);
        ExtUtilities.center(frame, dialog);
        dialog.setVisible(true);
    }
    
    private void displayNextCard() {
        int componentCount = display.getComponentCount();
        if(componentCount == 0) {
            Tuple2<Component, Function0<Boolean>> firstEntry = cards.get(0);
            setDisplayContent(firstEntry.getA());
            changeButtonStatedForCurrentCardCommitter();
        } else {
            Tuple2<Component, Function0<Boolean>> nextEntry  = getNextEntry(display.getComponent(0));
            if(nextEntry != null) {
                setDisplayContent(nextEntry.getA());
                changeButtonStatedForCurrentCardCommitter();
            }
        }
    }
    
    private void displayPreviousCard() {
        int componentCount = display.getComponentCount();
        if(componentCount == 0) {
            Tuple2<Component, Function0<Boolean>> firstEntry = cards.get(0);
            setDisplayContent(firstEntry.getA());
            changeButtonStatedForCurrentCardCommitter();            
        } else {
            Tuple2<Component, Function0<Boolean>> prevEntry  = getPrevEntry(display.getComponent(0));
            if(prevEntry != null) {
                setDisplayContent(prevEntry.getA());
                changeButtonStatedForCurrentCardCommitter();
            }
        }
    }
    
    public void executeOnCardWillShow(Component card, Runnable task) {
        onDisplayFunctions.put(card, task);
    }
    
    public void executeOnCardWillShow(String name, Runnable task) {
        onDisplayNameFunctions.put(name, task);
    }
    
    private void setDisplayContent(Component c) {
        Runnable task = onDisplayFunctions.get(c);
        if(task == null) task = onDisplayNameFunctions.get(c.getName());
        if(task != null) {
            task.run();
        } else {
            Logger.getLogger(WizardDialog.class.getName()).log(Level.INFO, "NO TASK FOR COMPONENT {0}", c.getName());
        }
        
        display.removeAll();
        display.add(c);
        display.revalidate();
        display.repaint();
        dialog.setTitle(c.getName() == null ? "Wizard" : c.getName());
    }

    private void changeButtonStatedForCurrentCardCommitter() {
        Function0<Boolean> committer = getCommitterForCurrentCard();
        if(committer != null) {
            boolean last = isCurrentCardTheLastOne();
            boolean first = isCurrentCardTheFirstOne();
            Boolean enableNext = committer.apply();
            next.setEnabled(enableNext);
            next.setText(last ? getEndLabel() : getNextLabel());
            prev.setEnabled(!first);
        }
    }
    
    private String getEndLabel() {
        return endLabel;
    }
    
    private String getNextLabel() {
        return nextLabel;
    }

    private Function0<Boolean> getCommitterForCurrentCard() {
        if(display.getComponentCount() == 0) return null;
        
        Component component = display.getComponent(0);
        for (Tuple2<Component, Function0<Boolean>> entry : cards) {
            if(entry.getA() == component) return entry.getB();
        }
        
        return null;
    }

    private boolean isCurrentCardTheLastOne() {
        Component component = display.getComponent(0);
        Tuple2<Component, Function0<Boolean>> lastEntry = cards.getLast();
        return component == lastEntry.getA();
    }

    private boolean isCurrentCardTheFirstOne() {
        Component component = display.getComponent(0);
        Tuple2<Component, Function0<Boolean>> firstEntry = cards.get(0);
        return firstEntry.getA() == component;
    }
    
    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() { public void run() {
            final JTextFieldExt t0 = new JTextFieldExt(20);
            final JTextFieldExt t1 = new JTextFieldExt(20);
            JPanelExt c0 = new JPanelExt(new BorderLayout())
                    .withName("Panel 0")
                    .setTitledBorder("First Panel");
            JPanelExt c1 = new JPanelExt(new BorderLayout())
                    .withName("Panel 1")
                    .setTitledBorder("Second Panel");
            c0.add(t0);
            c1.add(t1);
            WizardDialog wd = new WizardDialog();
            wd.addCard(c0, BorderLayout.NORTH, new Function0<Boolean>() {

                public Boolean apply() {
                    return !t0.isEmpty();
                }
            });
            wd.addCard(c1, BorderLayout.NORTH, new Function0<Boolean>() {

                public Boolean apply() {
                    return !t1.isEmpty();
                }
            });
            wd.showDialog(null, "Test", new Dimension(400, 400), new Runnable() {public void run() {
                System.out.println("commit");
            }});
        }});
    }

    private Tuple2<Component, Function0<Boolean>> getNextEntry(Component component) {
        for (int i = 0; i < cards.size(); i++) {
            Tuple2<Component, Function0<Boolean>> entry = cards.get(i);
            if(entry.getA() == component) {
                if(i < cards.size() - 1) {
                    return cards.get(i + 1);
                }
            }
        }
        return null;
    }
    
    private Tuple2<Component, Function0<Boolean>> getPrevEntry(Component component) {
        for (int i = 0; i < cards.size(); i++) {
            Tuple2<Component, Function0<Boolean>> entry = cards.get(i);
            if(entry.getA() == component && i > 0) {
                return cards.get(i - 1);
            }
        }
        return null;
    }

    private void displayNextCardOrCommit() {
        if(isCurrentCardTheLastOne()) {
            commit();
        } else {
            displayNextCard();
        }
    }

    private void displayPreviousCardIfPossible() {
        if(!isCurrentCardTheFirstOne()) {
            displayPreviousCard();
        }
    }
    
    private void commit() {
        stopPollerAndCloseDialog();   
        if(onCommit != null) {
            onCommit.run();
        }
    }

    private void stopPollerAndCloseDialog() {
        dialog.dispose();
        poller.stop();
    }
}
